package ru.spbau.database.group3.engine;

import ru.spbau.database.group3.memoryManager.BufferManager;
import ru.spbau.database.group3.memoryManager.PageAddress;
import ru.spbau.database.group3.memoryManager.SpaceManager;
import ru.spbau.database.group3.parser.ConditionOperator;
import ru.spbau.database.group3.parser.Constraint;
import ru.spbau.database.group3.queryEvaluator.OKResult;
import ru.spbau.database.group3.queryEvaluator.QueryResult;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

/**
 * @author: stas
 * @date: 2/2/13
 * @time: 3:47 PM
 */
public class DatabaseEngine {

	public QueryResult select(String tableName, List<String> colNames,
			List<Condition> conditions) throws IOException,
			ClassNotFoundException {
		checkTableName(tableName);
		Relation relation = relations.get(tableName);
		return relation.selectRequest(colNames, conditions);
	}

    public QueryResult update(String tableName, List<Condition> conditions,
                              List<String> cols, List<Object> values)
            throws IOException, ClassNotFoundException {
        checkTableName(tableName);
        Relation relation = relations.get(tableName);
        return relation.updateRequest(conditions, cols, values);
    }

    public QueryResult delete(String tableName, List<Condition> conditions)
            throws IOException, ClassNotFoundException {
        checkTableName(tableName);
        Relation relation = relations.get(tableName);
        return relation.deleteRequest(conditions);
    }

	public QueryResult insert(String tableName, List<Object> recordValues)
			throws IOException, ClassNotFoundException {
		checkTableName(tableName);
		Relation relation = relations.get(tableName);
		return relation.insertRequest(recordValues);
	}

	public QueryResult createTable(String tableName, List<Column> columns)
			throws IOException {
		if (relations.containsKey(tableName)) {
			throw new IllegalArgumentException("Table with name " + tableName
					+ " is already exists!");
		}
		SpaceManager.createTable(tableName);
		relations.put(tableName, new Relation(root + tableName + "/", columns,
				calculateRecordSize(columns)));
		return new OKResult();
	}

	public QueryResult createIndexRequest(String tableName, String indexName,
			List<String> columnNames, List<String> orders, String type)
			throws ClassNotFoundException, IOException {
		checkTableName(tableName);
		Relation relation = relations.get(tableName);
		if (type.toLowerCase().equals("hash")) {
			return relation.createHashIndexRequest(indexName, columnNames,
					orders, type);
		} else if (type.toLowerCase().equals("btree")) {
			return relation.createBtreeIndexRequest(indexName, columnNames,
					orders, type);
		} else {
			throw new IllegalArgumentException("Unsupported index type: "
					+ type);
		}
	}

	public QueryResult createBtreeIdnexRequest(String tableName,
			String indexName, List<String> columnNames, List<String> orders,
			String type) throws ClassNotFoundException, IOException {
		checkTableName(tableName);
		Relation relation = relations.get(tableName);
		return relation.createBtreeIndexRequest(indexName, columnNames, orders,
				type);
	}

	public static Condition createCondition(String tableName,
			Constraint constraint) {
		if (constraint.getConditionOperator() == ConditionOperator.EMPTY) {
			return null;
		}
		checkTableName(tableName);
		return relations.get(tableName).createCondition(constraint);
	}

	public static DatabaseEngine getInstance(String rootPath)
			throws IOException, ClassNotFoundException {
		DatabaseEngine.root = rootPath;
		loadState();
		return INSTANCE;
	}

	public static int calculateRecordSize(List<Column> columns) {
		int size = 0;
		for (Column c : columns) {
			size += c.getType().size();
		}
		return size;
	}

	public void dump() throws IOException {
		BufferManager.flushPool();
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
				root + SYSTEM_FILE_NAME));
		oos.writeObject(relations);
		SpaceManager.dump(oos);
		oos.close();
	}

	private DatabaseEngine() {
	}

	private static void loadState() throws ClassNotFoundException, IOException {
		Path systemPath = Paths.get(root, SYSTEM_FILE_NAME);
		if (Files.exists(systemPath)) {
			readState(systemPath);
		} else {
			setClearState();
		}
	}

	private static void checkTableName(String tableName) {
		if (relations.containsKey(tableName)) {
			return;
		}
		throw new IllegalArgumentException("There isn't table with name "
				+ tableName);
	}

	private static void setClearState() {
		relations = new HashMap<String, Relation>();
		SpaceManager.setState(root, new TreeMap<Integer, PageAddress>(),
				new HashMap<String, Integer>(), 0);
	}

	private static void readState(Path systemPath) throws IOException,
			ClassNotFoundException {
		ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(
				new FileInputStream(systemPath.toFile())));
		DatabaseEngine.relations = (Map<String, Relation>) ois.readObject();
		int idCounter = ois.readInt();
		Map<Integer, PageAddress> directory = (Map<Integer, PageAddress>) ois
				.readObject();
		Map<String, Integer> pageCounter = (Map<String, Integer>) ois
				.readObject();
		ois.close();
		SpaceManager.setState(root, directory, pageCounter, idCounter);
	}

	private static String root;

	private final static String SYSTEM_FILE_NAME = "SystemFile";
	private final static DatabaseEngine INSTANCE = new DatabaseEngine();
	private static Map<String, Relation> relations;

	// public QueryResult update(String tableName, Condition c,ArrayList<String>
	// columnNames, ArrayList<String> values) throws IOException,
	// ClassNotFoundException {
	// checkTableName(tableName);
	// spaceManager.setCurrentTable(root, tableName);
	// Table table = relations.get(tableName);
	// return table.updateRequest(columnNames,values, c);
	// }
	//
	// public QueryResult delete(String tableName, Condition c) throws
	// IOException, ClassNotFoundException {
	// checkTableName(tableName);
	// spaceManager.setCurrentTable(root, tableName);
	// Table table = relations.get(tableName);
	// return table.deleteRequest( c);
	// }





}
